import queue.BlockingObject;
import queue.PageBuffer;
import queue.PageToRetrieve;
import data.DataGatherer;


/**
 * Model which contains all of the functionality of the Spider program.
 * This class manages all of the objects required by the Spider.
 * 
 * Basically, we can use this class to execute the 
 * spider program with one command: execute(...)
 * 
 * @author Ken "David" McClain
 * @author Peter Mosher
 * @version 1
 */
public class ModelMulti {
	
	/** Constructor, does nothing. */
	public ModelMulti() { }
	
	/** 
	 * Starts the spidering process. Waits until the process is completed.
	 * This is the heart of the multi-threaded spider. 
	 * 
	 * @param pageLimit Maximum number of pages to retrieve.
	 * @param seedURL   Starting web page address.
	 * @param keywords  Collection of Strings to look for and key count of.
	 */
	public void execute(final int pageLimit, final String seedURL, 
						final String[] keywords, final int threadCount) {
		
		// This is a blocking object we will wait on (block on) and
		// be notified on (woken up on) by the PageRetriever and PageParser threads.
		BlockingObject finishedBlock = new BlockingObject();
		
		// The single DataGatherer object all PageParser objects will report to.
		final DataGatherer dg = new DataGatherer(pageLimit);
		
		// Instantiate the "URL" queue. Holds URLs to be retrieved.
		PageToRetrieve ptr = new PageToRetrieve(threadCount, dg);
		
		// PageToRetrive needs a reference to the blocking object we are using.
		ptr.setFinishedBlock(finishedBlock);
		
		// PageBuffer needs reference to PageToRetrieve object.
		PageBuffer pb = new PageBuffer(ptr, threadCount, keywords, dg);
		// PageBuffer needs a reference to the blocking object we are using.
		pb.setFinishedBlock(finishedBlock);
		
		// The PageToRetrieve queue needs a reference to the PageBuffer object.
		// This is so threads that retrieve pages can place them in the PageBuffer.
		ptr.setPageBuffer(pb);
		
		
		// Create threads of the queue objects.
		Thread ptrThread = new Thread(ptr);
		Thread pbThread = new Thread(pb);
		
		// Start the queue threads. They will wait for items to be added.
		ptrThread.start();
		pbThread.start();
		
		// Insert initial seed URL into queue. 
		// This will start the spider process.
		ptr.add(seedURL);
		
		do {
			// Old implementation used a form of spin-locking. This was bad!
			// Thread.sleep(1);
			
			// Instead, we 'wait' (block) on an object until a thread completes,
			// which wakes us up.  Then we check if we are done. If we aren't done, we wait longer.
			synchronized (finishedBlock) {
				try {
					finishedBlock.wait();
				} catch (InterruptedException ie) { }
			}
			
			// dg.hitLimit checks if the # of pages parsed is equal to the page limit
			// PageToRetrieve and PageBuffer have "isStopped" methods which check
			// if their queues are empty and the threads are inactive.
		} while (!dg.hitLimit() && !(ptr.isStopped() && pb.isStopped()));
		
		// Stop the other threads.  
		// This is done by setting a flag which breaks out of the threaded while loop.
		ptr.stop();
		pb.stop();
		
		// Let the user know we're waiting.
		try {
			Thread.sleep(25);
		} catch (InterruptedException ie) { }
		System.out.println("\n\nWaiting for threads to finish...");
		
		// Wait for threads to fully stop. It may not happen instantly.
		try{
			ptrThread.join();
			pbThread.join();
		} catch(InterruptedException ie) {  }
		
	}
	
}
